﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using TPOJ.Core.Utils;

namespace TPOJ.Core
{
    /// <summary>
    /// The judge engine.
    /// </summary>
    public class Engine
    {
        public string UserInputPath { get; set; }
        public string TempDir { get; set; }
        public string TestDataDir { get; set; }
        public Language Language { get; set; }
        public int TimeLimit { get; set; }
        public int MemoryLimit { get; set; }
        public string WarningMessage { get; set; }

        private string BinPath
        {
            get
            {
                return this.TempDir + "TPOJ_Tmp.exe";
            }
        }
        private string UserOutputPath
        {
            get
            {
                return this.TempDir + "TPOJ_Tmp.out";
            }
        }

        private string PERulesPath
        {
            get
            {
                return this.TestDataDir + "PERules.ini";
            }
        }

        /// <summary>
        /// Create a judge engine.
        /// </summary>
        /// <param name="userInputPath">User source file path</param>
        /// <param name="tempDir">Temporary directory for binary file and user output data file</param>
        /// <param name="testDataDir">Test data directory</param>
        /// <param name="language">The programming language used</param>
        /// <param name="timeLimit">Time limit</param>
        /// <param name="memoryLimit">Memory limit</param>
        public Engine(string userInputPath, string tempDir, string testDataDir, Language language, int timeLimit, int memoryLimit)
        {
            this.UserInputPath = userInputPath;
            this.TempDir = tempDir;
            this.TestDataDir = testDataDir;
            this.Language = language;
            this.TimeLimit = timeLimit;
            this.MemoryLimit = memoryLimit;
            InitializeEngine();
        }

        /// <summary>
        /// Initialize the engine to ensure all required directories or files exists.
        /// </summary>
        private void InitializeEngine()
        {
            if (!Directory.Exists(TempDir))
                Directory.CreateDirectory(TempDir);
            if (!Directory.Exists(TestDataDir))
                throw new ArgumentException("Test data directory does not exist.");
        }

        /// <summary>
        /// Perform the judge.
        /// </summary>
        /// <returns>The judge result</returns>
        public Result Judge()
        {
            ClearDirectories();
            Compiler compiler = new Compiler(this.UserInputPath, this.BinPath, this.Language);
            Result compilingResult, runningResult = null, comparingResult = null;
            compilingResult = compiler.Compile();
            WarningMessage = compilingResult.ErrorMessage;
            if (compilingResult.ExitCode != 0)
            {
                return compilingResult;
            }

            foreach (var file in Directory.GetFiles(this.TestDataDir, "*.in"))
            {
                Runner runner = new Runner(file, this.UserOutputPath, this.BinPath, this.TimeLimit, this.MemoryLimit, this.Language);
                runningResult = runner.Run();
                if (runningResult.ExitCode != 0)
                {
                    return runningResult;
                }

                Comparer comparer = new Comparer(file.GetOutputFileName(), this.UserOutputPath, this.PERulesPath);
                comparingResult = comparer.Compare();
                if (comparingResult.ExitCode != 0)
                {
                    return comparingResult;
                }
            }
            comparingResult.Time = runningResult.Time;
            comparingResult.Memory = runningResult.Memory;
            return comparingResult;
        }

        /// <summary>
        /// Clear Directories and files.
        /// Called at the beginning of judging process.
        /// </summary>
        private void ClearDirectories()
        {
            if (File.Exists(BinPath))
                File.Delete(BinPath);
            if (File.Exists(UserOutputPath))
                File.Delete(UserOutputPath);
            File.Create(UserOutputPath).Close();
        }
    }
}
